#ifndef _HELIX_OPEN_GL_API_H_
#define _HELIX_OPEN_GL_API_H_

#include <gl/gl.h>
#include <gl/glu.h>
#include "HObject.h"
#include "HVector.h"
#include "HGAPI.h"
#include "HTriMesh.h"
#include "HOpenGLExt.h"
#include "Glext.h"
#include "HGLTextureFont.h"
#include "HGLSLProgram.h"




class HOGLAPI : public HGAPI
{
protected:
	HGLTextureFont *text;	  //gives us the ability to write text to the screen
	HGLSLProgram *textShader; //the shader for the text
	HOGLExt *GLextensions;    //access to opengl extensions
    
	HGLSLProgram *lastShader;

	int maxTextureUnits;

public:
	HOGLAPI();             //constructor

	virtual ~HOGLAPI();    //destructor

	virtual bool SetVSync(bool VSyncOn);                       //sets vsync to on or off


	//functions related to points
	virtual void SetPointSize(float size);                       //update the point size state
	virtual void DrawPoint3f(float vert[3]);


	virtual void DrawTriMesh(HTriMesh *tmesh);             
	void DrawTriMeshVA(HTriMesh *tmesh);                        //draw the triangle mesh using vertex arrays
	virtual void DrawNormals(HTriMesh *tmesh, float length);	//draw the normals of the triangle mesh

	virtual void InitTextureFont(char *fileName);
	virtual void InitTextureFont(char *fileName,int width,int height,int charSpacing);
	virtual void SetFontParams(int charHeight,int charWidth, int charSpacing);
	virtual void SetFontShader(char *vertShader,char *fragShader);
	virtual void EnableFont();
	virtual void DisableFont();
	virtual void PrintF(char *str,...);
	virtual void PrintF(int x,int y,char *str,...);
	virtual void SetCursor(int xpx,int ypx);
};


HOGLAPI::HOGLAPI()
{
	APIType=H_OPEN_GL_API;          //use the openGL graphics library
	CalcNorms=0;					//don't calculate normals every frame
	VSyncEnabled=true;
	GLextensions = new HOGLExt();     //get access to opengl extensions
	GLextensions->Print();
	text=NULL;						//must initialize this somewhere else
	textShader=NULL;	
	lastShader=NULL;
	if(GLextensions->Supported(H_MULTI_TEX_SUPPORT))
		glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB,&maxTextureUnits);
	else
	{
		maxTextureUnits=1;
	}
}


HOGLAPI::~HOGLAPI()
{
	delete GLextensions;
	if(text)
		delete text;
	if(textShader)
		delete textShader;
}

bool HOGLAPI::SetVSync(bool VSyncOn)
{
	if(!GLextensions->Supported(H_SWAP_CONTROL))
	{   
		Log().Error("Cannot set VSync, Extension not supported");
		return false;        //cannot set VSync
	}
	else if (VSyncOn)
	{
		wglSwapIntervalEXT(1);   // turn it on
		VSyncEnabled=true;
	}
	else
	{
		wglSwapIntervalEXT(0);   // turn it off
		VSyncEnabled=false;
	}
	Log().Println("VSync enabled = %d",VSyncEnabled);
	return true;
}

//functions related to text
void HOGLAPI::InitTextureFont(char *fileName)
{
	if(text)
		delete text;
	text=new HGLTextureFont(fileName,10,10);
}

void HOGLAPI::InitTextureFont(char *fileName,int width,int height,int charSpacing)
{
	if(text)
		delete text;
	text=new HGLTextureFont(fileName,width,height);
	text->setCharSpacing(charSpacing);
}
	

void HOGLAPI::SetFontParams(int charHeight,int charWidth, int charSpacing)
{
//	text->createFont(charHeight,charWidth,charSpacing);
}

void HOGLAPI::SetFontShader(char *vertShader,char *fragShader)
{
	if(GLextensions->Supported(H_GLSL))
	{
		textShader=new HGLSLProgram();
		textShader->LoadVertShader(vertShader);
		textShader->LoadFragShader(fragShader);
		textShader->AttachShaderObjects();
		textShader->LinkProgram();
	}
	else
	{
		Log().Error("Cannot set texture font shader: GLSL not supported");
	}
}

void HOGLAPI::EnableFont()
{
	glEnable(GL_TEXTURE_2D);
	if(textShader)
	{
		lastShader=HGLSLProgram::currentProgram;
		textShader->UseProgram();
	}
	text->begin2DFont();
}

void HOGLAPI::DisableFont()
{
	if(textShader)
	{
		textShader->UnUseProgram();
		if(lastShader)
		{
			lastShader->UseProgram();
			lastShader=NULL;
		}
	}
	text->end2DFont();
}

void HOGLAPI::PrintF(char *str,...)
{
	char		text[256];			// Holds Our String
	va_list		ap;					// Pointer To List Of Arguments
	if (str == NULL)				// If There's No Text
		return;						// Do Nothing
	va_start(ap, str);				// Parses The String For Variables
    vsprintf(text, str, ap);		// And Converts Symbols To Actual Numbers
	va_end(ap);						// Results Are Stored In Text

	this->text->Print(text);
}

void HOGLAPI::PrintF(int x,int y,char *str,...)
{
	char		text[256];			// Holds Our String
	va_list		ap;					// Pointer To List Of Arguments
	if (str == NULL)				// If There's No Text
		return;						// Do Nothing
	va_start(ap, str);				// Parses The String For Variables
    vsprintf(text, str, ap);		// And Converts Symbols To Actual Numbers
	va_end(ap);						// Results Are Stored In Text

	this->text->Print(x,y,text);
}

void HOGLAPI::SetCursor(int xpx,int ypx)
{
	text->setPosition(xpx,ypx);
}

//functions related to points
void HOGLAPI::SetPointSize(float size)                       //update the point size state
{
	glPointSize(size);
}


void HOGLAPI::DrawPoint3f(float vert[3])
{
	glBegin(GL_POINTS);
		glVertex3fv(vert);
    glEnd();
}



/* NEEDS UPDATING FOR MULTITEXTURING SUPPORT        **
** Also, look into extension to make drawing faster */
void HOGLAPI::DrawTriMeshVA(HTriMesh *tmesh)
{
	bool availData[11];
	for(int i=0;i<11;i++)
		availData[i]=tmesh->IsAvailable(i);

	if(!availData[H_TMESH_DATA] && (!availData[H_TRI_DATA] || !availData[H_VERT_DATA]))
	{
		Log().Error("No data available.");
		return;          //exit, because there is not data available
	}
	if(!availData[H_TRI_DATA] && !availData[H_VERT_DATA])
	{
		tmesh->UnShareVerts();        //convert data to a triangle mesh
	}


	//this always has to be available
	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3,GL_FLOAT,0,tmesh->GetDataf(H_VERT_DATA));         //gets the vertex data

	//chech to see if normal data is available
	//if it is, enable and update the normal data
	if(availData[H_NORM_DATA])
	{
		glEnableClientState(GL_NORMAL_ARRAY);
		glNormalPointer(GL_FLOAT,0,tmesh->GetDataf(H_NORM_DATA));
	}

	//check to see if vertex color data is available
	//if it is, enable and set the vertex color data
	if(availData[H_VCOL_DATA])
	{
		glEnableClientState(GL_COLOR_ARRAY);
		int colorfloats=3;
		if(tmesh->UsingAlpha())
			colorfloats=4;
		glColorPointer(colorfloats,GL_FLOAT,0,tmesh->GetDataf(H_VCOL_DATA));
	}

	//More code should go here to check if multitextureing is available or not
	//check to see if texture coord data is available
	if(availData[H_TEXC1_DATA])
	{
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glTexCoordPointer(2,GL_FLOAT,0,tmesh->GetDataf(H_TEXC1_DATA));
	}

	if(availData[H_TANGENT_DATA] && availData[H_BITANGENT_DATA] && maxTextureUnits>=3)
	{
		glClientActiveTextureARB(GL_TEXTURE1_ARB);
		glTexCoordPointer(3, GL_FLOAT, 0, tmesh->GetDataf(H_TANGENT_DATA));
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glClientActiveTextureARB(GL_TEXTURE2_ARB);
		glTexCoordPointer(3, GL_FLOAT, 0, tmesh->GetDataf(H_TANGENT_DATA));
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	//The actual drawing code.  This renders the mesh and all of its states
	if(GLextensions->Supported(H_DRAW_RANGE_ELEMENTS))
	{
		glDrawRangeElementsEXT(GL_TRIANGLES,                   //element type
							   0,                              //minimum index in array
							   tmesh->GetNum(H_VERT_DATA),     //maximum index in array
							   tmesh->GetNum(H_TRI_DATA)*3,    //number of vertices to draw 
							   GL_UNSIGNED_INT,                //specifies how the indices are stored
							   tmesh->GetDatai(H_TRI_DATA));   //the index of vertices
	}
	else
	{
		glDrawElements(GL_TRIANGLES,tmesh->GetNum(H_TRI_DATA)*3,GL_UNSIGNED_INT,tmesh->GetDatai(H_TRI_DATA));
	}


	//disable all the client states
	glDisableClientState(GL_VERTEX_ARRAY);
	if(availData[H_NORM_DATA])
		glDisableClientState(GL_NORMAL_ARRAY);
    if(availData[H_VCOL_DATA])
		glDisableClientState(GL_COLOR_ARRAY);
	if(availData[H_TEXC1_DATA])
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	if(availData[H_TANGENT_DATA] && availData[H_BITANGENT_DATA] && maxTextureUnits>=3)
	{
		glClientActiveTextureARB(GL_TEXTURE1_ARB);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		glClientActiveTextureARB(GL_TEXTURE2_ARB);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}
}

void HOGLAPI::DrawTriMesh(HTriMesh *tmesh)
{

	//check to see what OGL extensions are available
	DrawTriMeshVA(tmesh);
}

void HOGLAPI::DrawNormals(HTriMesh *tmesh, float length)
{
	if(!tmesh->IsAvailable(H_VERT_DATA) || !tmesh->IsAvailable(H_NORM_DATA))
	{
		return;
	}

	float *verts=tmesh->GetDataf(H_VERT_DATA);
	float *norms=tmesh->GetDataf(H_NORM_DATA);


	glBegin(GL_LINES);
	for(int i=0;i<tmesh->GetNum(H_VERT_DATA);i++)
	{
		glNormal3f(norms[i*3],norms[i*3+1],norms[i*3+2]);
		glColor3f(0.0,1.0,0.0);
		glVertex3f(verts[i*3],verts[i*3+1],verts[i*3+2]);
		glColor3f(1.0,0.0,0.0);
		glVertex3f(verts[i*3]+length*norms[i*3],
			       verts[i*3+1]+length*norms[i*3+1],
				   verts[i*3+2]+length*norms[i*3+2]);
	}
	glEnd();
}

#endif
